12  Tables

In the previous chapters Chapter 9, Chapter 10 and Chapter 11, we covered in depth how you can use plots to communicate the results of your work. In this chapter, we introduce a second way to do so: using a table. In general, plots are often used to show trends over time (e.g. a line chart), a relationship (e.g. a scatter plot), to compare values across categories (e.g. a column of bar chart) or to show the full distribution of a variable (e.g. a boxplot or histogram). However, charts don’t always allow you to show exact or precise values. To see those values, people will have to look closely at the scales on the axis or the color/size/shape aesthetic you used. Doing so, they will usually not be able to assess precise values. To add those, you need to include labels or annotations. Doing so adds visual distractions to a chart and increases the risk that people don’t see the message of the plot: do you expect your audience to remember the trend of the exact values? Tables allows you to show precise values. Doing so, you can communicate e.g. the exact gross margin on a product. As long as the number of products is not too large, can can use a table to compare these margins across product categories or brands. In addition, you can add visual components including line- of fill colors, images, sparklines or other charts. However, tables are less suitable to show a large number of values.

In this chapter we will use {gt} (Iannone et al. (2025)) and {gtExtras} (Mock (2024)). The first allows you to build the table. The second package adds a number of functions that allows you to e.g. select table themes or add charts, images or icons to a table. In addition to these two packages, there are various packages that you can use for specific purposes: {gtsummary} (Sjoberg et al. (2021)) to generate tables allowing you to customize e.g. tables including estimates of regression models, summary statistics for data frames, … ; {stargazer} (Hlavac (2022)) to show multiple regression models side-by-side (see e.g. Princeton’s university library research guide); {flextable} (Gohel and Skintzos (2024)) which can be used to create tables ready for Word or Powerpoint (see e.g. the flextable gallery or flextable book).

In this chapter we will introduce {gt} and {gtExtras}. As you’ll see, the workflow to create a table is largely in line with {ggplot2}’s workflow to create charts: there are functions to set the components of the table, to format the cells and functions to change e.g. colors (fill, text, lines). Often these function share a lot of similarities with their equivalent in {ggplot2}. For instance, to format numbers the {scales} package includes functions to deal with percentages, currencies, … . Similar functions are also part of {gt}; both include custom themes and both also allow to design your own theme, … . Because of these similarities and as most of these functions where introduced in Chapter 11 or Chapter 10, we will not cover the {gt} or {gtExtras} functions in depth. Using the online reference material (Iannone et al. (2025) and Mock (2024)) you should be able to use all arguments of most functions. Here, we will focus on those functions where the approach differs. There are many additional guides on {gt} and [gtExtrax] available on the internet: Nicola Rennie’s Getting started with {gt} tables includes an example of inserting charts using {ggplot2} in a table; Riding tables with {gt} and {gtExtras} gives a nice illustration on how you can use {gt} and {gtExtras} to create a nice table with Tour de France riders and Creating beautiful tables in R with {gt} illustrates the full workflow including options to finetune output. Tom Mock’s gt cookbook discusses all major options to build tables. In addition, on his blog The Mockup you’ll find many specific illustrations, e.g. Beautiful tables in R with gtExtras, Functions and themes for gt tables and 10+ Guidelines for Better Tables in R.

You installed {gt} in Chapter 1. You can now also install {gtExtras}:

install.packages("gt", "gtExtras")

To use these packages, you have to load them

library(gt)
Warning: package 'gt' was built under R version 4.4.3
library(gtExtras)

If you would like to learn how {gtsummary}, {stargazer} or {flextable} work, you need to install them first.

Before we start with {gt} and {gtExtras} we first introduce the components of a table.

12.1 Components of a table

A table has rows and columns, at their intersectin, each table is divided in various cells. A table has various parts (Figure 12.1). Moving from the inside to the table to the outside, the table body refers to the cells that include the data. These cells can include numbers, characters, icons or images, charts, … . In Figure 12.1, you see that the table body also includes summary cells. The values in these summary cells can be included in your dataset or you can use {gt} to calculate these statistics. Moving up, the column labels show which variables are included in the cells of a column. These column labels may include the units of measurement (e.g. “usd”, “miles”, “hours”) if these would not be clear. Spanner column labels include labels that “span” multiple columns. For instance, if your dataset includes variables such as “sales (in units)”, “unit price” and “revenue” as well as “costs of goods sold” and “selling, general and administrative expenses” and “depreciation and amortization” you can include a spanner “revenues” above the first 3 columns and add a spanner “Operating costs” above the last three columns. You can have spanners at multiple levels: for instance: for each region in a country, you show the population and population density per city. Here, population and population density could be column labels. The level 1 spanner would include the city and the level 2 spanner - grouping cities across regions - would be the region. The stubs include the row labels and the group row labels. The allow you to “group” rows. For instance, if you have a dataset with income data for jobs (accountant, marketing manager, …) in various industries (banking, pharmaceuticals, automotive, retail, fashion, …) the group rows could show the industry and the rows the job. The cells to the right of the row labels include values; those to the right of the group row labels usually don’t. If the table includes summary statistics, their label (summary label) shows the statistics (e.g. average, median, …). The stub head shows which units are in the rows.

Figure 12.1: Components of a table

At the top of the table, you find the table header. This header includes the title and the subtitle. These should be short and explain what the type of data the table presents. If you need to add further explanatory notes, you can do so at the bottom of each table using one or more footnotes. These footnotes can be attached to a column or row label, a spanner column label, a row group label or an individual cell in the table body. The source notes allow you to add the source of the data. The footnotes and source notes are in the table footer. In addition, you can add a table caption, e.g. “table 1”.

Using {gt} you refer to these components using a function that looks like tab_"component", e.g. tab_header(), tab_spanner(), tab_row_group(), tab_stubhead(), tab_footnote(), tab_source_note() and tab_caption(). The arguments of function follow from the component they refer to, e.g. tab_header() includes title = and subtitle = as it main argument; tab_spanner() includes the label = for the spanner, the columns who are part of the spanner in columns = and the spanners = for the spanners that should be spanned over in case you have more than one level of column spanners; tab_footnote() requires a text in footnote =, a location in location = and a placement where you want to add the footnote (to the right or the left of the data, label, …) placement = c("auto", "right", "left"). {gt} uses a similar strategy to identify the cells, e.g. cells_title(groups = c(title", "subtitle")) allows you to identify the title or subtitle; cells_body(column = everything(), rows = everything()) allows you to identify one or more cells at the intersection of one or more rows and one or more columns and cell_row_groups(groups = everyting()) will be used to identify one or more row group labels. Using these, you can target individual cells. For instance, to add a footnote to a table body cell on the 4th row in the 3rd column you can set the exact location using tab_footnote(location = cells_body(column = 3, row = 4)).

To identify rows and column you can also use <tidy-select> syntax. Recall from Chapter 8 that this syntax includes the following functions that allows you to select one or more columns (Henry and Wickham (2024)):

  • starts_with(match, ignore.case = FALSE, vars = NULL)

  • ends_with(match, ignore.case = FALSE, vars = NULL)

  • contains(match, ignore.case = FALSE, vars = NULL)

  • matches(match, ignore.case = FALSE, vars = NULL)

    In all these functions match refers to an expression. If more than one expression is included, they should by included in c(). For the first three functions, the expression is exact (e.g. “var”, “new”, …). Using matchyou can also include regular expressions. The second argument, ignore.case = FALSE is familiar from other character functions. The last argument, vars allows you to include a vector of variable names. By default, the variable names are taken from the dataset.

  • num_range(prefix, range, suffix = "", width = NULL, vars = NULL)

    Using this function you can select variables such as var1x, var2x or x001, x002. To do so, you can include a prefix, e.g. “var”, a suffix (e.g. “x” in var1x) and a numerical range, e.g. 1:2. Adding a width allows you to select columns such as x002 where width in this case is 3 (x002 includes three numbers: 0, 0 and 2).

  • everyting(vars = NULL)

    Select all columns or those in the vector vars.

  • last_col(offset = 0L, vars = NULL)

    Select the last column by default. If offset is different from zero (e.g. 10), this function selects the 10nd variable from the end. By default, this function selects the last column from the dataset.

  • group_cols()

    Select the columns that are used to group a dataset.

  • all_of(), any_of(x, vars = NULL)

    Here, x is a vector with variable names. R will select all columns that are included in this vector. The first is strict: if one of the names in the vector doesn’t appear in the data, R will return a error. The second doesn’t. If you add not, !any_of can be used to remove variables from a dataset.

  • where(fn)

    The argument fn refers to a function that returns TRUE or FALSE. For instance, to select all numeric columns, where(is.numeric) can be used. Likewise, to select all variables whose minimum is negative: where(\(x) min(x) < 0) would select these columns.

12.2 {gt}: workflow

As was the case with {ggplot2}, {gt} uses a data frame or tibble as its input. As you enter the data frame into the gt() function, R creates a gt table object: gt_tbl. Using the pipe operator |> you can start to add components, e.g. a title and subtitle, a footnote, column labels, … change the columns (e.g. merging or moving), format the columns using one of the fmt_ functions fmt_number(), fmt_currency(), fmt_percent(), … change styling using tab_style(), tab_style_body() or one of the opt_ functions. Using these you can change the font used to show the value or label in a cell using cell_text(), the fill color of the cell using cell_fill() or, using cell_line(), the lines at the top, bottom, left and right of each cell. If none of the styling options offer what you need, you can always use tab_options() to change any element of the table you would like. This last function is equivalent to the themes() function {ggplot2}. Note that there is one major difference with {ggplot2}: {gt} using the pipe operator while {ggplot2} used the +. The reason for this difference is that the latter adds layers following the grammar of graphics while the former creates a gt_tbl object. Using the pipe operator, you pipe this object in the next function. In addition, the workflow in {ggplot2} was more or less governed by the grammar of graphics: you first identity the data and aesthetics, select the geometry, change the scales and guides, add facets and adjust themes. As a matter of fact, without aesthetics, you can not adjust the scales or introduce facets. For a table, the workflow allows for more degrees of freedom. For instance, you could start with the header then move on to the column labels, spanner labels, row and row group labels then adjust the formatting of the numbers in the table body but you can also reverse the order and start with the formatting.

If you are satisfied with the output, you can save the table using gtsave(). This function allows you to save a table as a docx, html, tex, rtf, … file). In addition, you can use as_word() to save a table in an Open Office XML format that you can import in e.g. Word.

{gt} allows you to use html or markdown options for e.g. column labels or table headings. To do so, you include the html code in html(text) where text refers to the text that R needs to preserve as html output. Using md(text), {gt} will interpret the text part as markdown formatting. Using this function it is easy to add quick styling. For instance adding you can add bold “text” using md("**text**"), italics “text” using md("*text*") and {gt} will in bold/italics “text” using md("***text***").

To illustrate the workflow, we will use life_table as the main dataset. You can import the dataset from your raw data folder:

life_table <- readr::read_csv(here::here("data", "raw", "life_table.csv"))
Rows: 9 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): iso2c, iso3c, country, region
dbl (4): 1980_gdp_capita, 1980_life_exp, 2020_gdp_capita, 2020_life_exp

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

The data frame includes a random sample of 3 countries and 3 regions. It includes iso2C, iso3c, country, region, 1980_gdp_capita, 2020_gdp_capita, 1980_life_exp and 2020_life_exp. With 3 regions and 3 countries per region, we have sufficient data to illustrate most of {gt}’s functions.

12.2.1 Getting started

As a first step, you introduce the data frame in the gt() function. This function has a couple of arguments:

gt(
  data,
  rowname_col = "rowname",
  groupname_col = dplyr::group_vars(data),
  process_md = FALSE,
  caption = NULL,
  rownames_to_stub = FALSE,
  row_group_as_column = FALSE,
  auto_align = TRUE,
  id = NULL,
  locale = getOption("gt.locale"),
  row_group.sep = getOption("gt.row_group.sep", " - ")
)

The data refer to the data frame or tibble. As this is the first argument of the function, you can use the pipe operator |> to introduce your dataset in this function. The next two arguments deal with the table stub. Using the first, you can include a column whose values will be used for the row labels. The second, groupname_col allows you to identify the column whose values will be used to add row group labels. Recall that a data frame can include row names. If this is the case, there is an alternative to add row labels: using rownames_to_stub = TRUE will add these row names as label to the plot. Using row_group_as_column you can determine if the row group labels will be put in a separate column. By default, this is not the case and they will be added in a dedicated row in the same column as the one that includes the row labels. The locale arguments includes the possibility to change, e.g. the language. Using this function with the life_table dataset and accepting all default values shows the gt_tbl object:

life_table |> gt()
iso2c iso3c country region 1980_gdp_capita 1980_life_exp 2020_gdp_capita 2020_life_exp
CN CHN China East Asia & Pacific 430.8554 64.42000 10358.170 78.07700
JP JPN Japan East Asia & Pacific 19334.3747 75.98902 34650.797 84.56000
HK HKG Hong Kong SAR, China East Asia & Pacific 12553.2179 74.65341 41451.326 85.49634
PT PRT Portugal Europe & Central Asia 10832.5150 71.21463 19778.714 80.97561
BG BGR Bulgaria Europe & Central Asia 3431.5213 71.15756 7990.693 73.65854
DE DEU Germany Europe & Central Asia 23977.2699 72.80032 42362.647 81.04146
NI NIC Nicaragua Latin America & Caribbean 1850.0056 57.91400 1959.788 71.79500
GT GTM Guatemala Latin America & Caribbean 3287.0240 56.02400 4005.839 71.79700
TT TTO Trinidad and Tobago Latin America & Caribbean 8612.1558 67.57200 15789.614 74.40600

The table includes the variable names as column labels. The variable names are shown as they appear in the data. By default, {gt} also adds a number of horizontal lines: at the top and bottom of the column label cells and at the bottom of the table. The table doesn’t include any vertical lines. For now, we will remove the iso2c and iso3c columns. As we have the country name in the variable country, these variables don’t add value. To do so, we use the select function from {dplyr} and assign this data set to life_table_gt.

life_table_gt <- life_table |> 
  dplyr::select(country, region, starts_with(c("1980", "2020")))

The table now looks like

life_table_gt |> gt()
country region 1980_gdp_capita 1980_life_exp 2020_gdp_capita 2020_life_exp
China East Asia & Pacific 430.8554 64.42000 10358.170 78.07700
Japan East Asia & Pacific 19334.3747 75.98902 34650.797 84.56000
Hong Kong SAR, China East Asia & Pacific 12553.2179 74.65341 41451.326 85.49634
Portugal Europe & Central Asia 10832.5150 71.21463 19778.714 80.97561
Bulgaria Europe & Central Asia 3431.5213 71.15756 7990.693 73.65854
Germany Europe & Central Asia 23977.2699 72.80032 42362.647 81.04146
Nicaragua Latin America & Caribbean 1850.0056 57.91400 1959.788 71.79500
Guatemala Latin America & Caribbean 3287.0240 56.02400 4005.839 71.79700
Trinidad and Tobago Latin America & Caribbean 8612.1558 67.57200 15789.614 74.40600

We can add row labels - labels that identify the observations in the rows - using the option rowname_col. Here, we can add the values in the variable country to use as row labels:

life_table_gt |> gt(rowname_col = "country") 
region 1980_gdp_capita 1980_life_exp 2020_gdp_capita 2020_life_exp
China East Asia & Pacific 430.8554 64.42000 10358.170 78.07700
Japan East Asia & Pacific 19334.3747 75.98902 34650.797 84.56000
Hong Kong SAR, China East Asia & Pacific 12553.2179 74.65341 41451.326 85.49634
Portugal Europe & Central Asia 10832.5150 71.21463 19778.714 80.97561
Bulgaria Europe & Central Asia 3431.5213 71.15756 7990.693 73.65854
Germany Europe & Central Asia 23977.2699 72.80032 42362.647 81.04146
Nicaragua Latin America & Caribbean 1850.0056 57.91400 1959.788 71.79500
Guatemala Latin America & Caribbean 3287.0240 56.02400 4005.839 71.79700
Trinidad and Tobago Latin America & Caribbean 8612.1558 67.57200 15789.614 74.40600

Although the table didn’t change, much, there are some notable differences with the case where we didn’t add row names: the table now includes the country names as row labels but doesn’t add a stub head (the reference to “country” disappeared) and the table now includes a vertical line separating the row labels from the table body.

If the data include related observations, you can collect those related observations under a row group. In the examplme, the data frame includes a variable region which we can use to add row group labels. Doig so, the countries will be grouped according to their region and {gt} will add a row group label with the value for each region. To do so, we have multiple options:

  • add the row group label in a dedicated row in the same column as the row labels:
life_table_gt |> gt(groupname_col = "region")
country 1980_gdp_capita 1980_life_exp 2020_gdp_capita 2020_life_exp
East Asia & Pacific
China 430.8554 64.42000 10358.170 78.07700
Japan 19334.3747 75.98902 34650.797 84.56000
Hong Kong SAR, China 12553.2179 74.65341 41451.326 85.49634
Europe & Central Asia
Portugal 10832.5150 71.21463 19778.714 80.97561
Bulgaria 3431.5213 71.15756 7990.693 73.65854
Germany 23977.2699 72.80032 42362.647 81.04146
Latin America & Caribbean
Nicaragua 1850.0056 57.91400 1959.788 71.79500
Guatemala 3287.0240 56.02400 4005.839 71.79700
Trinidad and Tobago 8612.1558 67.57200 15789.614 74.40600
  • add the row group label in a new column:
life_table_gt |> gt(groupname_col = "region", row_group_as_column = TRUE)
country 1980_gdp_capita 1980_life_exp 2020_gdp_capita 2020_life_exp
East Asia & Pacific China 430.8554 64.42000 10358.170 78.07700
Japan 19334.3747 75.98902 34650.797 84.56000
Hong Kong SAR, China 12553.2179 74.65341 41451.326 85.49634
Europe & Central Asia Portugal 10832.5150 71.21463 19778.714 80.97561
Bulgaria 3431.5213 71.15756 7990.693 73.65854
Germany 23977.2699 72.80032 42362.647 81.04146
Latin America & Caribbean Nicaragua 1850.0056 57.91400 1959.788 71.79500
Guatemala 3287.0240 56.02400 4005.839 71.79700
Trinidad and Tobago 8612.1558 67.57200 15789.614 74.40600

For both options, including rowname_col = "country" would add the country names as row labels. Doing so, R would add a vertical lines between these labels and the table body. With one dedicated column per row group, the table would include two vertical lines: one separating the row group labels from the row labels and one separating the row labels from the table body.

Here, we used an existing variable in the table to add row groups. You can also create these groups without such a variable. To do so, you use the tab_row_group() function: tab_row_group(data, label, rows, id = label). In addition to the data, this function needs a label for the row group (label = "") and well as the rows to add to group (rows =). You can further specify an id for the row group. By default {gt} uses the label as id. If you add an id, e.g. id = rg1, you can use that id in subsequent operation, e.g. to style that row group. To identify rows, you can use e.g. <tidy-select> syntax or filter rows using boolean operators. To illustrate, we”ll add two groups: “high income” and “low income” and filter rows for the first group as those where 2020 per capita GDP is higher than 22000 and group countries whose 2020 per capita GDP is lower than or equal to 22000 under the “low income label). Note that the level (22000) is chosen arbitrarily. To add these labels we identify rows using rows = '2020_gdp_capita > 22000 for the first label and rows = '2020_gdp_capita <= 22000 for the second.

life_table_gt |> gt() |>
  tab_row_group(
    group = "high income", 
    rows = `2020_gdp_capita` > 22000) |>
  tab_row_group(
    group = "low income", 
    rows = `2020_gdp_capita` <= 22000)
Warning: Since gt v0.3.0 the `group` argument has been deprecated.
• Use the `label` argument to specify the group label.
This warning is displayed once every 8 hours.
country region 1980_gdp_capita 1980_life_exp 2020_gdp_capita 2020_life_exp
low income
China East Asia & Pacific 430.8554 64.42000 10358.170 78.07700
Portugal Europe & Central Asia 10832.5150 71.21463 19778.714 80.97561
Bulgaria Europe & Central Asia 3431.5213 71.15756 7990.693 73.65854
Nicaragua Latin America & Caribbean 1850.0056 57.91400 1959.788 71.79500
Guatemala Latin America & Caribbean 3287.0240 56.02400 4005.839 71.79700
Trinidad and Tobago Latin America & Caribbean 8612.1558 67.57200 15789.614 74.40600
high income
Japan East Asia & Pacific 19334.3747 75.98902 34650.797 84.56000
Hong Kong SAR, China East Asia & Pacific 12553.2179 74.65341 41451.326 85.49634
Germany Europe & Central Asia 23977.2699 72.80032 42362.647 81.04146

Using boolean operators, you can add more refined row groups, e.g. rows = var1 < x & (var2 > y | var3 < z) would collect rows where the first variable is smaller than x and the second variable is larger than y or the third variable is smaller than z.

Let’s return to the table without row groups or row labels. Recall that this table includes the variables 1980_gdp_capita and 2020_gdp_capita. However, these are now shown next to each other: the data frame shows the “1980” variable for both per capita GDP and life expectancy at birth first while it shows their “2020” values in the last two columns. Here, {gt} uses the order as they appear in the dataset. Suppose you would like to show per capita GDP and life expectancy next to each other. In other words, you would like to table to show the “1980” and “2020” values for per capita gdp first and those for life expectancy in the last two columns. There are two ways to do this. First there is the cols_move() function in {gt}:

cols_move(data, columns, after)

Here you can identify the columns you like to move as well as a location using after. You can identify the columns referring to their name, include them in a vector or use <tidy-select> syntax. For instance, to move the column “2020_gdp_capita” after “1980_gdp_capita” and using <tidy-select> syntax (starts_with()):

life_table_gt |> gt() |> cols_move(starts_with("2020_gdp"), after = `1980_gdp_capita`)
country region 1980_gdp_capita 2020_gdp_capita 1980_life_exp 2020_life_exp
China East Asia & Pacific 430.8554 10358.170 64.42000 78.07700
Japan East Asia & Pacific 19334.3747 34650.797 75.98902 84.56000
Hong Kong SAR, China East Asia & Pacific 12553.2179 41451.326 74.65341 85.49634
Portugal Europe & Central Asia 10832.5150 19778.714 71.21463 80.97561
Bulgaria Europe & Central Asia 3431.5213 7990.693 71.15756 73.65854
Germany Europe & Central Asia 23977.2699 42362.647 72.80032 81.04146
Nicaragua Latin America & Caribbean 1850.0056 1959.788 57.91400 71.79500
Guatemala Latin America & Caribbean 3287.0240 4005.839 56.02400 71.79700
Trinidad and Tobago Latin America & Caribbean 8612.1558 15789.614 67.57200 74.40600

The second option would be to use {dplyr}’s relocate() function and change the data frame used to pipe into gt():

life_table_gt <- life_table_gt |> 
  dplyr::relocate(`2020_gdp_capita`, .after = `1980_gdp_capita`)

12.2.2 Formatting

The dataset includes two variables measured in USD (per capita gdp for 1980 and 2020) and two numeric variables. As you could in {ggplot2}, e.g. using {scales} (see Chapter 11), you can add formatting of various “standard” formats in {gt} as well. For instance, using fmt_currency() and fmt_number() you can change the formatting of values in the columns that include currency or numeric values. Before we do so to format the table, let’s review the arguments of the latter function:

fmt_number(
  data,
  columns = everything(),
  rows = everything(),
  decimals = 2,
  n_sigfig = NULL,
  drop_trailing_zeros = FALSE,
  drop_trailing_dec_mark = TRUE,
  use_seps = TRUE,
  accounting = FALSE,
  scale_by = 1,
  suffixing = FALSE,
  pattern = "{x}",
  sep_mark = ",",
  dec_mark = ".",
  force_sign = FALSE,
  system = c("intl", "ind"),
  locale = NULL
)

The first three arguments should be familiar: they allow to identify the data as well as the columns and rows. By default, {gt} using everything(). In other words fmt_number() formats all rows of every column. If you add specific columns, e.g. using one of the <tidy-select> helper functions, the column name, a vector with column names of their column number, {gt} will format all rows for these columns. Likewise, if you identify a specific rows, R will format all columns for each of these rows. Setting both, R will format the cells that are at the intersection of the identified columns and rows. The next 6 arguments allow you to specify the number of decimals, the number of significant figures, to determine if the table needs to drop trailing zero’s (redundant zeros after the decimal mark), if the the table needs to drop trailing decimal marks (if FALSE, the table will show 15. and not 15), if there is a thousand separator identified in sep_mark = (different from the one in the locale) and if the table needs to show negative values within brackets (accounting = TRUE). The scale argument works as it did in {ggplot2}. As an alternative to scale, you can use suffixing. Here you can supply {gt} with T (trillion), B (billion) M (million) andK (thousand). R will scale the values accordingly and add a suffix. For instance, using suffixing = "M", the table will show 7.5M if the data includes 7500000. As an alternative, you can add suffixing = TRUE and R will determine the appropriate scale factor. If you specify a pattern using {x} for the value, the table will show that pattern e.g. the pattern “approx {x}” would add “approx” to every value. The arguments sep_markand dec_mark allow you to specify the decimal mark (by default a dot) and the thousand separator (by default a comma). If you change the locale, these default values will change. However, adding a specif mark here overrules the locale. If you want to add a plus sign to positive values, you can change the value force_sign from FALSE into TRUE. For the system, by default it used the international system where thousands are separated (i.e. three digits). The alternative is the Indian system.

The arguments of fmt_currency() are very similar

fmt_currency(
  data,
  columns = everything(),
  rows = everything(),
  currency = NULL,
  use_subunits = TRUE,
  decimals = NULL,
  drop_trailing_dec_mark = TRUE,
  use_seps = TRUE,
  accounting = FALSE,
  scale_by = 1,
  suffixing = FALSE,
  pattern = "{x}",
  sep_mark = ",",
  dec_mark = ".",
  force_sign = FALSE,
  placement = "left",
  incl_space = FALSE,
  system = c("intl", "ind"),
  locale = NULL
)

Here, the argument use_subunits = TRUE shows sub-units, e.g. cent for the Euro. Changing this into FALSE removes these sub-units e.g. €15.12. becomes €15. {gt} includes a wide range of currencies and symbols. To add the Euro symbol for instance, you can use currency = "EUR". Doing so, R will add a “€” sign to the values in the columns and rows. By default, it will add these symbols left. To change that into right you can change placement = "left" into "right". R supports the following symbols:

info_currencies(type = "symbol")
Currencies Supported in gt
Currency symbols are used in the fmt_currency() function.

Currency Symbol Keyword Formatted Currency
dollar $49.95
euro €49.95
pound £49.95
yen ¥49.95
franc ₣49.95
lira ₤49.95
peseta ₧49.95
won ₩49.95
sheqel ₪49.95
dong ₫49.95
kip ₭49.95
tugrik ₮49.95
drachma ₯49.95
peso ₱49.95
guarani ₲49.95
austral ₳49.95
hryvnia ₴49.95
cedi ₵49.95
rupee ₹49.95
generic ¤49.95

To see which currencies {gt} supports, you can use info_currencies(begins_with = ). For instance, to show all currencies that start with “a”:

info_currencies(begins_with = "a")
Currencies Supported in gt
Currency codes are used in the fmt_currency() function

Currency
Name
Alpha
Code
Numeric
Code
No. of
Subunits
Formatted
Currency
United Arab Emirates dirham AED 784 2 DH49.95
Afghan afghani AFN 971 2 ؋49.95
Albanian lek ALL 8 2 Lek49.95
Armenian dram AMD 51 2 AMD49.95
Netherlands Antillean guilder ANG 532 2 NAƒ49.95
Angolan kwanza AOA 973 2 Kz49.95
Argentine peso ARS 32 2 $49.95
Australian dollar AUD 36 2 $49.95
Aruban florin AWG 533 2 ƒ49.95
Azerbaijani manat AZN 944 2 ₼49.95

Using the three digit alpha code, R will show the currencies with their symbol.

There are many other fmt_ function including e.g. fmt_percent, fmt_date, fmt_time, fmt_datetime for the date and time (for the style for date/time options, see the guide in the {gt}’s online material), fmt_fraction, fmt_roman (to show Roman number, e.g. IV, XII) and a general fmt() function. Here, you can add a function e.g. paste (e.g. fns = \(x) paste("[", x/2, "]") to divide the values by 2 and add square brackets. For these function, most of the arguments follow from their format and you can use Iannone et al. (2025) to see them.

To illustrate using live_table_gt let’s add a currency format to the gdp variables and a number format to the life expectancy format. In both cases, we will show two decimals. For the currency, we will use the dollar and add a white space as separator mark:

life_table_gt |> gt() |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD", 
    sep_mark = " ") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2)
country region 1980_gdp_capita 2020_gdp_capita 1980_life_exp 2020_life_exp
China East Asia & Pacific $430.86 $10 358.17 64.42 78.08
Japan East Asia & Pacific $19 334.37 $34 650.80 75.99 84.56
Hong Kong SAR, China East Asia & Pacific $12 553.22 $41 451.33 74.65 85.50
Portugal Europe & Central Asia $10 832.52 $19 778.71 71.21 80.98
Bulgaria Europe & Central Asia $3 431.52 $7 990.69 71.16 73.66
Germany Europe & Central Asia $23 977.27 $42 362.65 72.80 81.04
Nicaragua Latin America & Caribbean $1 850.01 $1 959.79 57.91 71.80
Guatemala Latin America & Caribbean $3 287.02 $4 005.84 56.02 71.80
Trinidad and Tobago Latin America & Caribbean $8 612.16 $15 789.61 67.57 74.41

To change the currency values using suffixing and show life expectancy with 1 significant digit after the decimal we can use suffixing = TRUE in the currency formatting function and n_sigfig = 3 in the number formatting function.

life_table_gt |> gt() |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD", 
    suffixing = TRUE) |>
  fmt_number(
    columns = contains("life"), 
    n_sigfig = 3)
country region 1980_gdp_capita 2020_gdp_capita 1980_life_exp 2020_life_exp
China East Asia & Pacific $430.86 $10.36K 64.4 78.1
Japan East Asia & Pacific $19.33K $34.65K 76.0 84.6
Hong Kong SAR, China East Asia & Pacific $12.55K $41.45K 74.7 85.5
Portugal Europe & Central Asia $10.83K $19.78K 71.2 81.0
Bulgaria Europe & Central Asia $3.43K $7.99K 71.2 73.7
Germany Europe & Central Asia $23.98K $42.36K 72.8 81.0
Nicaragua Latin America & Caribbean $1.85K $1.96K 57.9 71.8
Guatemala Latin America & Caribbean $3.29K $4.01K 56.0 71.8
Trinidad and Tobago Latin America & Caribbean $8.61K $15.79K 67.6 74.4

Here, you can see that {gt} scales the values with 1/1000 and adds a “K”. For the number of years, the table now shows 1 digit after the decimal sign and R rounded using the usual rounding rules (Chapter 3).

There are a couple of useful functions to deal with missing values, zeros or very large and very small values. The first sub_missing() allows you to substitute missing values with a different character. By default the table will show “—”, however, you can change that if you add a specific character or white space. As with all other functions, you can use this substitution for specific columns/rows. A similar function is sub_zero(). This function allows you to replace “0”-s in the table. By default the table will add “nil”. Using sub_small_vals() you can substitute small values, i.e. values below a threshold which you can set as one of this function’s arguments with a alternative pattern, e.g. “<0.01”. By default, R uses 0.01 as a threshold and changes all values smaller than this threshold to “<0.01”. In other words, 0.0025, 0.00025 will all be set to “<0.01”. In a table that includes significance levels for e.g. a t-statistics or regression estimates, this is a useful way to change values such as 0.0000 into “<0.01”. sub_large_vals() performs a similar calculations for very large values.

In addition to the format, you can also align the columns left, right or center. To do so, you can use cols_align(align = , columns = ) where you add “left”, “center” or “right” after align and identify the columns after the columns = argument e.g. using the <tidy-select> syntax, a vector with column names or a vector with column positions. For numeric values, you can further use cols_align_decimal(columns = , dec_mark = ".", locale = ). Using this function {gt} will align the columns using the decimal mark specified in dec_mark = or the decimal mark that follows from the locale argument. This last function is usually only relevant if you didn’t specify the number of decimals and the number of decimals is different for the elements in the column’s cells. To illustrate, we’ll align the numeric columns using cols_align_decimal and the character columns using cols_align. Using the latter, we’ll center the character columns:

life_table_gt |> gt() |>
  fmt_currency(
    scale_by = 1,
    columns = 3:4,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_align(
    columns =  where(is.character), 
    align = "center") |>
  cols_align_decimal(
    columns = where(is.numeric))
country region 1980_gdp_capita 2020_gdp_capita 1980_life_exp 2020_life_exp
China East Asia & Pacific    $430.86 $10,358.17 64.42 78.08
Japan East Asia & Pacific $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China East Asia & Pacific $12,553.22 $41,451.33 74.65 85.50
Portugal Europe & Central Asia $10,832.52 $19,778.71 71.21 80.98
Bulgaria Europe & Central Asia  $3,431.52  $7,990.69 71.16 73.66
Germany Europe & Central Asia $23,977.27 $42,362.65 72.80 81.04
Nicaragua Latin America & Caribbean  $1,850.01  $1,959.79 57.91 71.80
Guatemala Latin America & Caribbean  $3,287.02  $4,005.84 56.02 71.80
Trinidad and Tobago Latin America & Caribbean  $8,612.16 $15,789.61 67.57 74.41

12.2.3 Labels, headers, footnotes and sources

Before we continue, let’s first assign the formatted table to an object life_table_gt_form. Doing so, we can use this table object with formatted columns as the starting point in our code. Here, we will use 2 decimals for both the number of currency format:

life_table_gt_form <- life_table_gt |> gt() |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2)

The table includes the variable names as they appear in the data set. To change the column labels, you can use cols_label(). Here, you have two options. The first is to add labels of each column where you would like to change the label. To illustrate Markdown, we’ll add bold to the label for the variables country and region and change the other ones referring only to their years and written in italics. To add these bold and italic fonts, using md() you add the part you would like in bold between double stars and the parts you would like in italics between single stars. For every old name you include a new name using old name = new name:

life_table_gt_form |>
  cols_label(
    country = md("**Country**"), 
    region = md("**Region**"), 
    `1980_gdp_capita` = md("*1980*"),
    `2020_gdp_capita` = md("*2020*"),
    `1980_life_exp` = md("*1980*"),
    `2020_life_exp` = md("*2020*"))
Country Region 1980 2020 1980 2020
China East Asia & Pacific $430.86 $10,358.17 64.42 78.08
Japan East Asia & Pacific $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China East Asia & Pacific $12,553.22 $41,451.33 74.65 85.50
Portugal Europe & Central Asia $10,832.52 $19,778.71 71.21 80.98
Bulgaria Europe & Central Asia $3,431.52 $7,990.69 71.16 73.66
Germany Europe & Central Asia $23,977.27 $42,362.65 72.80 81.04
Nicaragua Latin America & Caribbean $1,850.01 $1,959.79 57.91 71.80
Guatemala Latin America & Caribbean $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago Latin America & Caribbean $8,612.16 $15,789.61 67.57 74.41

The table now shows new column labels: “1980”, “2020”, “1980” and “2020”. There is a second way to change column labels. Most variable names in a dataset include a part which you can use for your labels. In 1980_life_exp for instance, the variable name includes “1980” but also “life”, “exp” or “life_exp”. We can use these parts to build column labels. To do so, we first extract the column names from the dataset using colnames(). The result is a vector that includes the variable names as they appear in the data. We can now extract the parts of these names that we need. For instance, in this dataset, we need the 1980 and 2020. To extract these parts, we can use one of the string function (Chapter 3 or Chapter 4). Here we can use e.g. stringr::str_remove() to remove the part “_gdp_capita” and “life_exp”. As an alternative, you can use str_extract_all() or grep(value = TRUE) to extract the strings you want to include in the labels of the variables. We now have a vector with names we like to include in the table. Using this vector, we can add formatting. Here, we do so using str_to_title, alternatives are toupper, tolower … or you can replace e.g. life by “life expectancy” using a string replace function. The result is a vector with variables names as we would like them in the table. In the last step, we create a named vector where the names of the variables as they appear in the data are used to name the labels we want in the table. Do do so, we use names() to add names to the names_for_table vector. The result is a vector where the column names are the names as they appear in the dataset and the values are the names as they should be used in the table.

names_in_data <- colnames(life_table_gt)
names_for_table <- names_in_data |> stringr::str_remove("_[a-z]+_[a-z]+")
names_for_table <- names_for_table |> stringr::str_to_title()
names(names_for_table) <- names_in_data

We can now use this vector, names_for_table in the cols_label() argument. To do so, we add .list = names_for_table. Using this named vector, R will use the names of every column to identify the names as they appear in the dataset. If there is a match with the names in the dataset, R will replace these names with the values in the vector. In other words, it will replace the names as they appear in the data with the names as they should show in the table. If there is no match, R will not changese these labels.

life_table_gt_form |>
  cols_label(.list = names_for_table)
Country Region 1980 2020 1980 2020
China East Asia & Pacific $430.86 $10,358.17 64.42 78.08
Japan East Asia & Pacific $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China East Asia & Pacific $12,553.22 $41,451.33 74.65 85.50
Portugal Europe & Central Asia $10,832.52 $19,778.71 71.21 80.98
Bulgaria Europe & Central Asia $3,431.52 $7,990.69 71.16 73.66
Germany Europe & Central Asia $23,977.27 $42,362.65 72.80 81.04
Nicaragua Latin America & Caribbean $1,850.01 $1,959.79 57.91 71.80
Guatemala Latin America & Caribbean $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago Latin America & Caribbean $8,612.16 $15,789.61 67.57 74.41

The column labels are set. We can now add spanner columns. To do so, we use tab_spanner(). To add these spanners, we have to identify to columns for each spanner. To do so, we use the <tidy-select> helpers. For instance, using contains("gdp") to identify all columns for the first spanner, R will “year_per capita gdp”. For the second spanner using we use contains("life") to collect all the “year_life expectancy” columns. There are many alternatives: you can refer to the columns by their number (e.g. c(3, 4), 3:4) or in a vector by their (old) name. The spanner label is added after the label argument in tab_spanner(). Here,we use Markdown (md()) to show them in bold by including the label between **. As an alternative, you can use a traditional “character label” and add styling later.

life_table_gt_form |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`))
Country Region
Per capita GDP
Life expectancy
1980 2020 1980 2020
China East Asia & Pacific $430.86 $10,358.17 64.42 78.08
Japan East Asia & Pacific $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China East Asia & Pacific $12,553.22 $41,451.33 74.65 85.50
Portugal Europe & Central Asia $10,832.52 $19,778.71 71.21 80.98
Bulgaria Europe & Central Asia $3,431.52 $7,990.69 71.16 73.66
Germany Europe & Central Asia $23,977.27 $42,362.65 72.80 81.04
Nicaragua Latin America & Caribbean $1,850.01 $1,959.79 57.91 71.80
Guatemala Latin America & Caribbean $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago Latin America & Caribbean $8,612.16 $15,789.61 67.57 74.41

The table includes column and spanner labels. Now we can add a header using tab_header() and a source using tab_source_note(). The first allows you to set a title and a subtitle after the title = and subtitle = arguments. For the second, you add the text of the note after the source_note argument. To add a footnote, {gt} includes the tab_footnote() function. Here, you have to add the text of the footnote after the footnote argument as well as the location and the placement of the footnote. For the first, you have to identify where you want to show the footnote. Do so so, you can use e.g cells_title(groups = "title" or "subtitle") to add a note to the title or subtitle, cells_stubhead() to add the footnote to the stub head, cells_column_labels(columns = ) to add a note to one or more column labels identified in columns, cells_column_spanner(spanners = ) to add a note to one or more column spanners, … . Here we will add a note to the data for Germany for 1980 to explain that these data refer to Western Germany. To do so we use cells_body() and use the columns and rows argument to identify the exact table body cells. The note should be added to the columns including (“1980”) and the rows that show data for Germany. Here, we will add the names of the columns in a vector. Note that starts_with("1980"), contains("1980"), … could have been used as an alternative. For the rows, we identify them using rows = country == "Germany". Note that for the last argument, you start with rows = and then add the condition column == value. The condition can include multiple boolean operations. With respect to the placement, you can add the footnote reference “right” or “left” of the content of the cell. By default, R uses numbers. You can change these marks using tab_options(footnotes.marks = ). Here, you can add e.g. letters, LETTERS, extended or standard. The first two use the letters vectors and add lower or upper case letters while the last two use symbols: an Asterisk, a dagger, a double dagger, a section sign, a double vertical line and a paragraph sign. The first, extended uses all 6, the second uses only the first four. To add the headers, source note and footnote to the table and using numbers for footnote marks, we add these to the previous table:

life_table_gt_form |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right")
GDP per capita and life expectancy at birth
Selected countries and years
Country Region
Per capita GDP
Life expectancy
1980 2020 1980 2020
China East Asia & Pacific $430.86 $10,358.17 64.42 78.08
Japan East Asia & Pacific $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China East Asia & Pacific $12,553.22 $41,451.33 74.65 85.50
Portugal Europe & Central Asia $10,832.52 $19,778.71 71.21 80.98
Bulgaria Europe & Central Asia $3,431.52 $7,990.69 71.16 73.66
Germany Europe & Central Asia $23,977.271 $42,362.65 72.801 81.04
Nicaragua Latin America & Caribbean $1,850.01 $1,959.79 57.91 71.80
Guatemala Latin America & Caribbean $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago Latin America & Caribbean $8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Let’s now add the group rows back to the table. We can add them as a separate row in the same column the row labels:

life_table_gt |> gt(groupname_col = "region") |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

or use a separate column to show these row group labels:

life_table_gt |> gt(
  groupname_col = "region",
  row_group_as_column = TRUE) |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Using a spanner, we added a label on top of two or more columns. You can also merge to columns. Merging columns is useful when your dataset includes e.g. two variables that show the boundaries of a confidence interval (e.g. 1.96 standard deviation below and above the mean). You can leave these values as they are and add a spanner label “confidence interval” but you could also merge these two columns and show the values as e.g. [lower, higher] or (lower - higher) To do so, {gt} includes the cols_merge() function:

cols_merge(
  data,
  columns,
  hide_columns = columns[-1],
  rows = everything(),
  pattern = NULL)

As always, the first argument is the data. The second argument identifies the columns to merge. The third argument, hide_columns = columns[-1] shows the column label to hide. By default hides all but the first columns. To see this, recall from Chapter 4 that if you subset a vector with a minus sign, R shows all but the values on the index position with the minus sign. Here, as R hides all columns expect those not in the hide_columns argument, using columns[-1] hides all, but the first. In addition to the columsn, you can also target rows. The last argument shows the pattern for the merged column values. Here, you identify columns using {1} for the first column in the columns argument, {2} for the second, … . Adding a pattern, e.g. “{1} - {2}” will show the merged columns as “value_col1 - value_col2”. Although the life expectancy dataset doesn’t include variable where you would often use merged column, to illustrate, we’ll merge the gdp columns and use a pattern [1980gdp - 2020gdp]. Because there is not need for a spanner label, we will remove the spanner label for gdp:

life_table_gt |> gt() |>
  fmt_currency(
    scale_by = 1,
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right") |>
  cols_merge(
    columns = 3:4,
    pattern = "[{1} - {2}]") 
GDP per capita and life expectancy at birth
Selected countries and years
Country Region 1980
Life expectancy
1980 2020
China East Asia & Pacific [$430.86 - $10,358.17] 64.42 78.08
Japan East Asia & Pacific [$19,334.37 - $34,650.80] 75.99 84.56
Hong Kong SAR, China East Asia & Pacific [$12,553.22 - $41,451.33] 74.65 85.50
Portugal Europe & Central Asia [$10,832.52 - $19,778.71] 71.21 80.98
Bulgaria Europe & Central Asia [$3,431.52 - $7,990.69] 71.16 73.66
Germany Europe & Central Asia [$23,977.27 - $42,362.65]1 72.801 81.04
Nicaragua Latin America & Caribbean [$1,850.01 - $1,959.79] 57.91 71.80
Guatemala Latin America & Caribbean [$3,287.02 - $4,005.84] 56.02 71.80
Trinidad and Tobago Latin America & Caribbean [$8,612.16 - $15,789.61] 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Note in the output that {gt} also reformats the footnote: it add the the number after (recall the footnote was placed on the right) the pattern used to show the merged cells. R uses the label of the first column for the merged column label. We can change that label and also format other elements of this column, e.g. re-align. To do the first, we’ll use cols_label and set a new label of this column. To do the second, we use cols_align and use that function to center this merged column

life_table_gt |> gt() |>
  fmt_currency(
    scale_by = 1,
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right") |>
  cols_merge(
    columns = 3:4,
    pattern = "[{1} - {2}]") |>
  cols_label(
    `1980_gdp_capita` = "Per capita gdp") |>
  cols_align(
    columns = contains("gdp"), 
    align = "center")
GDP per capita and life expectancy at birth
Selected countries and years
Country Region Per capita gdp
Life expectancy
1980 2020
China East Asia & Pacific [$430.86 - $10,358.17] 64.42 78.08
Japan East Asia & Pacific [$19,334.37 - $34,650.80] 75.99 84.56
Hong Kong SAR, China East Asia & Pacific [$12,553.22 - $41,451.33] 74.65 85.50
Portugal Europe & Central Asia [$10,832.52 - $19,778.71] 71.21 80.98
Bulgaria Europe & Central Asia [$3,431.52 - $7,990.69] 71.16 73.66
Germany Europe & Central Asia [$23,977.27 - $42,362.65]1 72.801 81.04
Nicaragua Latin America & Caribbean [$1,850.01 - $1,959.79] 57.91 71.80
Guatemala Latin America & Caribbean [$3,287.02 - $4,005.84] 56.02 71.80
Trinidad and Tobago Latin America & Caribbean [$8,612.16 - $15,789.61] 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Let’s return to the table with un-merged columns. Here, you can see that the columns for “Per capita GDP” and “Life expectancy at birth” have a different width. As R tries to show the table using as little space as possible and life expectancy data only need 5 positions (2 decimals, a dot and 2 decimals) while the gdp columns all require more; R limits the width of the former. Sometimes you don’t want that and you columns with the same width. Using cols_width() you can specify the columns and add their width using ~px(value) where px refers to pixels. Here, we will set the width of the gdp and life columns equals to 100 pixels:

life_table_gt |> gt(groupname_col = "region") |>
  fmt_currency(
    scale_by = 1,
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_label(.list = names_for_table) |>
   tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right") |>
  cols_width(
    contains("gdp") ~px(100), 
    contains("life") ~px(100))
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

As an alternative to the pixel size, you can also use pct(x) which changes the size of a component (e.g. text or width) with x%.

12.2.4 Summary data

Tables often show summary data. {gt} allows you to add summary statistics to the table where it used the values in the table body to calculate these statistics. Before we show how you can do this, we’ll first save the table as we have it now and add the summary to this table:

life_table_gt_lab <- life_table_gt |> gt(groupname_col = "region") |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = c(`1980_life_exp`, `2020_life_exp`)) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right")

There are two ways to add summary data: using grand_summary_rows() you add one block of summary statistics whose values will be calculated from all rows in every column you identify:

grand_summary_rows(
  data,
  columns = everything(),
  fns = NULL,
  fmt = NULL,
  side = c("bottom", "top"),
  missing_text = "---")

The arguments of this function, in addition to the data, identify the columns for which you want to calculate summary statistics. By default, {gt} calculates summary statistics for all column in the data (columns = everything()). You can change this using a vector with column names, column positions or using the <tidy-select> syntax. Using the second argument, fns = you add which summary statistics you would like to show. To do so, you add these in a list. This list includes the label that you will see in the table as well as the function to use. For the latter, you use the ‘tilde’ approach and add a dot to show R where it needs to add the data for each column. For instance, to add the mean variable for a column with label “Average”, you can use Average = ~mean(., na.rm = TRUE ). In the fmt argument, you specify the formatting of the values in the summary columns and rows. Here, you use one of the fmt_() functions to format e.g. currencies, numbers, percentages. To tell R it needs to use the data from the summary function, you start these function with a tilde and add a dot inside the formatting function. In addition, you can add the side("bottom" or "top"). The first is the default and adds the summary rows at the bottom of the table. Using missing_text = " " you can specify how {gt} should show the values in the columns where there are no summary statistics.

To illustrate, let’s add the average, minimum and maximum values for each numeric column and use the same format as the one for the values in the body of the table. To to so, we add the grand_summary_rows(), specify the columns and functions and use fmt_ functions to format. Without the latter, {gt} would show the summary rows using default formatting.

life_table_gt_lab |>
  grand_summary_rows(
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD")) |>
  grand_summary_rows(
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2))
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average $9,367.66 $19,816.40 67.97 77.98
Minimum $430.86 $1,959.79 56.02 71.80
Maximum $23,977.27 $42,362.65 75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

To add statistics per group (or a number of groups) {gt} includes summary_rows(). Here the arguments are identical to those for grand_summary_rows() with one difference: in the former you can specify the groups, while in the latter this is not the case. In other words, with summary_rows you can add summary data for one specific group in the table. Here, we’ll show summary statistics for all groups (groups = everything()):

life_table_gt_lab |>
  summary_rows(
    groups = everything(), 
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD")) |>
  summary_rows(
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2))
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average $10,772.82 $28,820.10 71.69 82.71
Minimum $430.86 $10,358.17 64.42 78.08
Maximum $19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average $12,747.10 $23,377.35 71.72 78.56
Minimum $3,431.52 $7,990.69 71.16 73.66
Maximum $23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average $4,583.06 $7,251.75 60.50 72.67
Minimum $1,850.01 $1,959.79 56.02 71.80
Maximum $8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

To illustrate some of the options: let’s add summary statistics for per capita GDP for East Asia and Pacific and Europe and Central Asia and for life expectancy for Latin America & Caribbean and remove the default lines for missing values and use empty cells to show these missing values:

life_table_gt_lab |>
  summary_rows(
    groups = contains("Asia"), 
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD"), 
    missing_text = "") |>
  summary_rows(
    groups = contains("Latin"), 
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2),
    missing_text = "")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10

Minimum
$430.86 $10,358.17

Maximum
$19,334.37 $41,451.33

Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35

Minimum
$3,431.52 $7,990.69

Maximum
$23,977.27 $42,362.65

Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average


60.50 72.67
Minimum


56.02 71.80
Maximum


67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

The table now includes a limited set of summary statistics for both variables in both years. In addition, missing values - e.g. cells without a summary statistic - are now shown as empty.

12.2.5 Highlighting cells

You can highlight specific cells in the body of the table by fill the background of the cell with a color. Before we illustrate how you can do this, we”ll first save the table where we showed grand summary rows to an new table object life_table_gt_sum and add these visual cues to that table:

life_table_gt_sum <- life_table_gt_lab |>
  grand_summary_rows(
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD")) |>
  grand_summary_rows(
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2), 
    missing_text = "")

The first way to color cells is to use the data_color function from the {gt} package:

data_color(
  data,
  columns = everything(),
  rows = everything(),
  direction = c("column", "row"),
  target_columns = NULL,
  method = c("auto", "numeric", "bin", "quantile", "factor"),
  palette = NULL,
  domain = NULL,
  bins = 8,
  quantiles = 4,
  levels = NULL,
  ordered = FALSE,
  na_color = NULL,
  alpha = NULL,
  reverse = FALSE,
  fn = NULL,
  apply_to = c("fill", "text"),
  autocolor_text = TRUE,
  contrast_algo = c("apca", "wcag")

Most of the arguments are straightforward: in addition to the data, you need to add in which columns and rows {gt} will add a color. In addition, with direction you determine if coloring applies to the rows or columns. By default, this is the column. The method refers to how {gt} needs to set the colors for different values. By default (auto) {gt} will use numeric for numeric data and factor if data are factors. With bin R will determine the number of colors from the bins argument. By default, the number of bins is 8. The methods quantile sets the number of colors equal to the number of quantiles set in the quantiles argument. By default, this value is 4. Changing this to two, R would use 2 colors: one to show values below the median and one for values larger than the median. For the method factor, you can specify the levels in levels. If these factors are ordered, you can change ordered = FALSE in TRUE. In that case {gt} will order the colors in line with the factors. If there are missing values, you can set the color is these cells using na_color. With the palette function you set the color palette. Here, you can use e.g. a viridis, ColorBrewer or paletter color palette. In addition you can add your own colors using a vector. If you add a domain, values outside of that domain will be be shown using the palette colors. With alpha you change the transparency and using reverse you change the order. You can set the background color of the cell or the text. By default, {gt} fills the background color. Setting apply_to to “text”, applies the color to the text. With fn you can use e.g. {scales} functions to map colors to ranges.

To illustrate these arguments, we’ll use life_table_gt_sum and highlight the columns for including gdp data using 4 quantiles and the colorBrewer palette “YlOrRd” (Yellow - Orange - Red). To do so, we use <tidy-select> syntax contains("gdp") to select the columns, set method = quantile and use quantiles = 4. The palette is palette = "YlOrRd". We’ll use the viridis color palette to fill the columns with life expectancy data. Here, we include a domain which sets the values to color between 50 and 85. The dataset includes one value for life expectancy that falls outside of this range. As R treats those as “NA”, we can set a different color to highlight these values. Here we use “yellow” but you could any other color.

life_table_gt_sum |>
  data_color(
    columns = contains("gdp"), 
    method = "quantile", 
    palette = "YlOrRd",
    quantiles = 4) |>
  data_color(
    columns = contains("life"),
    method = "numeric",
    palette = "viridis",
    reverse = TRUE,
    domain = c(50, 85), 
    na_color = "yellow")
Warning: Some values were outside the color scale and will be treated as NA
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$9,367.66 $19,816.40 67.97 77.98
Minimum
$430.86 $1,959.79 56.02 71.80
Maximum
$23,977.27 $42,362.65 75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

The result now shows the table where cells are filled with a color. Note that {gt} sets the color of the text to keep it as visible as possible. For instance, all values in the gdp columns filled with yellow or orange are shown in black text, while those in red are shown with a while colored text. The value “85.50” for Hong Kong is shown in yellow. To change the color of the text and not the fill color, we need to add the apply_to = "text" argument. To illustrate, we’ll use the ggthemr::solarized palette (included in {paletteer} to color the text for life expectancy data and use red and green to color gdp data. We’ll show gdp using 2 bins:

life_table_gt_sum |>
  data_color(
    columns = contains("life"),
    method = "numeric",
    palette = "ggthemr::solarized",
    reverse = TRUE, 
    apply_to = "text") |>
  data_color(
    columns = contains("gdp"), 
    method = "bin", 
    palette = c("red", "green"),
    bins = 2, 
    apply_to = "text")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$9,367.66 $19,816.40 67.97 77.98
Minimum
$430.86 $1,959.79 56.02 71.80
Maximum
$23,977.27 $42,362.65 75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Recall that the data_color() function includes the target_column = argument. Here, you can fill a column’s cells using values in another column. In other words and in our example you could fill the column cells for life expectancy using e.g. data for gdp. To do you, you include the values to determine the color in columns and the columns to fill in target_columns. For instance, using the data for 1980 per capita gdp to color 1980 life expectancy and 2020 per capita gdp to color 2020 life expectancy using colorBrewer “Blues” for the first and “Reds” for the second, the table shows:

life_table_gt_sum |>
  data_color(
    columns = contains("1980_gdp"),
    target_columns = contains("1980_life"),
    method = "numeric",
    palette = "Blues") |>
  data_color(
    columns = contains("2020_gdp"),
    target_columns = contains("2020_life"),
    method = "numeric",
    palette = "Reds")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$9,367.66 $19,816.40 67.97 77.98
Minimum
$430.86 $1,959.79 56.02 71.80
Maximum
$23,977.27 $42,362.65 75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

A useful application of this approach is to color an “empty” column. To do so, we first create an empty column in the table. You can do this using the mutate(empty_col = "") and introduce a new data frame to the gt() function or you can use cols_add() function. Using the latter, you set the set the name of the column and its values as well as its position using e.g. .after or .before. Here, we will illustrate the latter approach. We’ll add two columns: col_gdp and col_life, keep them empty (e.g. set their values using "") and position them after the 2020 gdp and life columns. To include these columns in the spanner label, we have to adjust the code for life_table_gt_sum: we add the two empty columns after the formatting functions fmt_ using cols_add(col_gdp = "", .after = contains("2020_gdp)) and cols_add(col_life = "", .after = contains = ("2020_life")) and include these columns in the spanner. We keep all other components of the table as in life_table_gt_sum and assign this new table to live_table_gt_sum2:

life_table_gt_sum2 <- life_table_gt |> gt(groupname_col = "region") |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = contains("life"), 
    decimals = 2) |>
  cols_add(
    col_gdp = "", .after = contains("2020_gdp")) |>
  cols_add(
    col_life = "", .after = contains("2020_life")) |>
  cols_label(.list = names_for_table) |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = contains("life")) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "Source: World Bank Development indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)", 
    cells_body(columns = c(`1980_gdp_capita`, `1980_life_exp`), rows = country == "Germany"), 
    placement = "right") |>
  grand_summary_rows(
    columns = contains("gdp_capita"),
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD"),
    missing_text = "") |>
  grand_summary_rows(
    columns = contains("life_exp"),
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2), 
    missing_text = "")

life_table_gt_sum2
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 col_gdp 1980 2020 col_life
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$9,367.66 $19,816.40
67.97 77.98
Minimum
$430.86 $1,959.79
56.02 71.80
Maximum
$23,977.27 $42,362.65
75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

As you can see the table now includes two empty columns: col_gdp and col_life. We can now fill these columns using 2020 per capita gdp and 2020 life expectancy using data_color. To do so, we identify the columns to use using the fact that they both include 2020 and the target columns using the fact that they both include “col”. On the last lines, we remove the label and change the width of both these colored columns:

life_table_gt_sum2 |>
    data_color(
    columns = contains("2020"),
    target_columns = contains("col"),
    method = "numeric",
    palette = "viridis") |>
  cols_label(
    col_gdp = "", 
    col_life = "") |>
  cols_width(
    contains("col") ~ px(10))
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$9,367.66 $19,816.40
67.97 77.98
Minimum
$430.86 $1,959.79
56.02 71.80
Maximum
$23,977.27 $42,362.65
75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

The table now includes two columns whose color reflects the values in the column immediately to the left. Using these, you can now visualize where countries differ: for instance if a country shows a “good” color for per capita gdp, but doesn’t show the same color for life expectancy, it suggests that it does good in gdp, but less so on life expectancy.

{gtExtras} includes two functions that you can use to highlight rows (gt_highlight_rows) or columns (gt_highlight_cols). Both functions allows you to set the fill color, transparency, font weight (“normal”, “lighter”, “bolder” or a numeric variable between 1 and 1000) as well as the font color. In addition, these functions also include the option to set a target column. To illustrate, we’ll use gt_highlight_rows() to highlight all countries whose life expectancy in 2020 was higher than 80 using a fill color “steelblue” and setting the font color to “white” and the font weight to “bolder”. To select the rows, we use rows = 2020_life_exp >= 80:

life_table_gt_sum2 |>
  gt_highlight_rows(
    rows = `2020_life_exp` >= 80,
    fill = "steelblue", 
    font_weight = "bolder",
    font_color = "white")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 col_gdp 1980 2020 col_life
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$9,367.66 $19,816.40
67.97 77.98
Minimum
$430.86 $1,959.79
56.02 71.80
Maximum
$23,977.27 $42,362.65
75.99 85.50
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Using gt_highlight_cols() you can perform similar tasks for the columns.

12.2.6 Finetuning: adding images and charts

{gt} and {gtExtras} both include functions that allow you to add images or graphs to a table. To illustrate we’ll use the same countries and variables as in the previous examples, but now use the country iso2 and iso3 codes included in iso2c and iso3c:

life_table_gt_2 <- life_table |> dplyr::select(iso2c, iso3c, region, starts_with(c("1980", "2020"))) |> dplyr::relocate(`2020_gdp_capita`, .after = `1980_gdp_capita`)

Using this data frame in gt() with the region as row group variable, the labels for the iso-variables set to an empty value and otherwise identical to the the previous example, the table now looks as:

life_table_gt_3 <- life_table_gt_2 |>
  gt(groupname_col = "region") |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  fmt_number(
    columns = 5:6, 
    decimals = 2) |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "World Bank Development Indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)",
    cells_body(columns = 1, rows = 6), 
    placement = "right") |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = contains("life")) |>
  cols_label(
    iso2c = "",
    iso3c = "",
    `1980_gdp_capita` = md("*1980*"),
    `2020_gdp_capita` = md("*2020*"),
    `1980_life_exp` = md("*1980*"),
    `2020_life_exp` = md("*2020*"))

life_table_gt_3
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
CN CHN $430.86 10,358.17 64.42 78.07700
JP JPN $19,334.37 34,650.80 75.99 84.56000
HK HKG $12,553.22 41,451.33 74.65 85.49634
Europe & Central Asia
PT PRT $10,832.52 19,778.71 71.21 80.97561
BG BGR $3,431.52 7,990.69 71.16 73.65854
DE1 DEU $23,977.27 42,362.65 72.80 81.04146
Latin America & Caribbean
NI NIC $1,850.01 1,959.79 57.91 71.79500
GT GTM $3,287.02 4,005.84 56.02 71.79700
TT TTO $8,612.16 15,789.61 67.57 74.40600
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

The first function we will use is fmt_country()

  data,
  columns = everything(),
  rows = everything(),
  pattern = "{x}",
  sep = " ",
  locale = NULL)

This function uses the iso-codes in the column included in the columns and rows identified in columns = and rows = to add full country names to the table. Doing so, it replaces the iso codes. This function supports 242 regions. Using the locale to set a locale, these names will be shown in the language of the locale. You can add a pattern to the name in pattern. Here you use {x} to identify the country. By default, {gt} shows the country name. Using this function with life_table_gt_3 with the iso-codes in iso3c:

life_table_gt_3 |>
  fmt_country(columns = "iso3c")
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
CN China $430.86 10,358.17 64.42 78.07700
JP Japan $19,334.37 34,650.80 75.99 84.56000
HK Hong Kong $12,553.22 41,451.33 74.65 85.49634
Europe & Central Asia
PT Portugal $10,832.52 19,778.71 71.21 80.97561
BG Bulgaria $3,431.52 7,990.69 71.16 73.65854
DE1 Germany $23,977.27 42,362.65 72.80 81.04146
Latin America & Caribbean
NI Nicaragua $1,850.01 1,959.79 57.91 71.79500
GT Guatemala $3,287.02 4,005.84 56.02 71.79700
TT Trinidad & Tobago $8,612.16 15,789.61 67.57 74.40600
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

As you can see, the table now includes the country names and not the country iso3 codes.

With fmt_flag() you can add the national flags of a country. This function supports the same regions and includes the same arguments. In addition, you can set the height of the flag using the argument height. To illustrate using the first column with iso2 codes for the flags and the second column for the country names:

life_table_gt_3 |>
  fmt_country(columns = 2) |>
  fmt_flag(columns = 1)
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 10,358.17 64.42 78.07700
Japan $19,334.37 34,650.80 75.99 84.56000
Hong Kong $12,553.22 41,451.33 74.65 85.49634
Europe & Central Asia
Portugal $10,832.52 19,778.71 71.21 80.97561
Bulgaria $3,431.52 7,990.69 71.16 73.65854
1 Germany $23,977.27 42,362.65 72.80 81.04146
Latin America & Caribbean
Nicaragua $1,850.01 1,959.79 57.91 71.79500
Guatemala $3,287.02 4,005.84 56.02 71.79700
Trinidad & Tobago $8,612.16 15,789.61 67.57 74.40600
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

To show the flags closer to the country name, we can merge the first two columns:

life_table_gt_3 |>
  fmt_country(columns = 2) |>
  fmt_flag(columns = 1) |>
  cols_merge(columns = 1:2)
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 10,358.17 64.42 78.07700
Japan $19,334.37 34,650.80 75.99 84.56000
Hong Kong $12,553.22 41,451.33 74.65 85.49634
Europe & Central Asia
Portugal $10,832.52 19,778.71 71.21 80.97561
Bulgaria $3,431.52 7,990.69 71.16 73.65854
Germany1 $23,977.27 42,362.65 72.80 81.04146
Latin America & Caribbean
Nicaragua $1,850.01 1,959.79 57.91 71.79500
Guatemala $3,287.02 4,005.84 56.02 71.79700
Trinidad & Tobago $8,612.16 15,789.61 67.57 74.40600
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

In addition to flags, {gt} and {gtExtras} include functions to add images () to a table. For instance, using {gtExtra}’s gt_img_rows(gt_object, columns, img_source = "web", height = 30) you can add images to the table in gt_object. gt_img_rows will add the images to the columns in columns. The images can be stored on the “web” or “local” and by default, their height is 30. To illustrate, we’ll update an example shown in Albert Rapp’s Creating beautiful tables in R with {gt} and create a table which includes pictures of the recent prime ministers of the United Kingdom. The dataset includes the name of the prime minister in the first column as well as a web location where you can find a picture in the second:

pm_data <- tibble::tribble(
  ~Name, ~Image,
  "Keir Starmer", "https://upload.wikimedia.org/wikipedia/commons/9/91/Prime_Minister_Keir_Starmer_Portrait_%28cropped%29.jpg",
  "Rishi Sunak", "https://upload.wikimedia.org/wikipedia/commons/thumb/0/09/Rishi_Sunak%27s_first_speech_as_Prime_Minister_Front_%28cropped%29.jpg/1024px-Rishi_Sunak%27s_first_speech_as_Prime_Minister_Front_%28cropped%29.jpg",
  "Liz Truss", "https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Liz_Truss_official_portrait_%28cropped%292.jpg/292px-Liz_Truss_official_portrait_%28cropped%292.jpg",
  "Boris Johnson", "https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/Boris_Johnson_official_portrait_%28cropped%29.jpg/288px-Boris_Johnson_official_portrait_%28cropped%29.jpg", 
  "Theresa May", "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Official_portrait_of_Baroness_May_of_Maidenhead_crop_2.jpg/1024px-Official_portrait_of_Baroness_May_of_Maidenhead_crop_2.jpg")

To create a table, we first change the data frame into an gt_tbl object and add this table to the gt_img_rows function:

pm_data |>
  gt() |>
  gt_img_rows(columns = 'Image', height = 100)
Name Image
Keir Starmer
Rishi Sunak
Liz Truss
Boris Johnson
Theresa May

In addition to images or emoji’s, {gtExtras} includes a number of functiosn to add a chart to a table. Using gt_plt_dumbbell() it is possible to change the values in two columns in a dumbbell chart. To do you, you add the first and last value of the dumbbell in the arguments col1 and col2. You can further specify the label and the color palette. The first color is used to fill the first point of the dumbbell chart, the second to fill the second point and the third color to set the color of the horizontal line. The chart will show, the values. you can set the size and other formats of these values using text_args and text_size:

gt_plt_dumbbell(
  gt_object,
  col1 = NULL,
  col2 = NULL,
  label = NULL,
  palette = c("#378E38", "#A926B6", "#D3D3D3"),
  width = 70,
  text_args = list(accuracy = 1),
  text_size = 2.5
)

To see what these default values show, let’s add a dumbbell charts for life expectancy:

life_table_gt_3 |>
  gt_plt_dumbbell(col1 = `1980_life_exp`, col2 = `2020_life_exp`)
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020 1980
East Asia & Pacific
CN CHN $430.86 10,358.17 6478
JP JPN $19,334.37 34,650.80 7685
HK HKG $12,553.22 41,451.33 7585
Europe & Central Asia
PT PRT $10,832.52 19,778.71 7181
BG BGR $3,431.52 7,990.69 7174
DE1 DEU $23,977.27 42,362.65 7381
Latin America & Caribbean
NI NIC $1,850.01 1,959.79 5872
GT GTM $3,287.02 4,005.84 5672
TT TTO $8,612.16 15,789.61 6874
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

The table needs some additional layout: e.g. adding country names and flags; removing the 1980 life expectancy labels, … but in general, this table illustrates the use of a chart as part of a table. This is an example of a chart which uses values in the table. In the Try this out “Adding other plots” we include two additional examples to add plots to a table

For a dumbbell chart, you could use the data in the table. For other charts, you need more data. In other words, you have to use a data frame which includes, in addition to the data you would like to show in the table, the data you want to add to e.g. a sparkline, a density plot or a bullet plot. To so so, we will use the fact that a data frame is essentially a list (Chapter 4). In other words, we can add a column which includes list. In the example, to add a sparkline showing all life expectancy data for all years for each of the 9 countries, we have to add a column which includes these values. We will do this in various steps.

First we identify the unique countries in our data. To do so, we use unique(live_table$iso3c). This function returns the unique iso3 codes for the dataset we use. In the second step, we import the full life expectancy at birth dataset. This dataset includes, for all countries, all data for each year. As we don’t need all data for all countries but only the data for the 9 countries in the table, we use filter to filter from the full dataset only those countries we want to show in the table. This is the first inline of code after importing the full dataset. In the third step, we add a column which includes all values for life expectancy at birth for each of the 9 countries in the dataset. To do so, we first group_by country and then add a column using mutate(). This column includes a list will all data in the variable life_exp. Because we used group_by this list will include data per country in the group_by variable. We now have a dataset which includes a new variable: data_spark. For each country, the values of this variable are a list with all life expectancy at birth values per country.
In the fifth step, we will merge this dataset with the dataset we used to create the tables. As we only want to add the values for life expectancy, we can select the two columns to keep: one column that we can use to join and the data_spark column with the values to use for the sparkline. This is the last step in this code.

countries <- unique(life_table$iso3c)
life_df <- readr::read_csv(here::here("data", "raw", "life_df.csv"))
Rows: 13671 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): iso2c, iso3c, country, region
dbl (4): date, gdp_capita, life_exp, pop

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
life_table_dataspark <- life_df |> 
  dplyr::filter(iso3c %in% countries) |> 
  dplyr::group_by(iso3c) |>
  dplyr::mutate(data_spark = list(life_exp)) |>
  dplyr::select(iso3c, data_spark)

Using this data frame we can now add the data for the sparkline to the life_table dataset that we used to create the tables. To do so, we use inner_join:

life_table_spark <- life_table |> dplyr::inner_join(life_table_dataspark, by = "iso3c", multiple = "first")

life_table_spark now includes all variables we need:

str(life_table_spark)
spc_tbl_ [9 × 9] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ iso2c          : chr [1:9] "CN" "JP" "HK" "PT" ...
 $ iso3c          : chr [1:9] "CHN" "JPN" "HKG" "PRT" ...
 $ country        : chr [1:9] "China" "Japan" "Hong Kong SAR, China" "Portugal" ...
 $ region         : chr [1:9] "East Asia & Pacific" "East Asia & Pacific" "East Asia & Pacific" "Europe & Central Asia" ...
 $ 1980_gdp_capita: num [1:9] 431 19334 12553 10833 3432 ...
 $ 1980_life_exp  : num [1:9] 64.4 76 74.7 71.2 71.2 ...
 $ 2020_gdp_capita: num [1:9] 10358 34651 41451 19779 7991 ...
 $ 2020_life_exp  : num [1:9] 78.1 84.6 85.5 81 73.7 ...
 $ data_spark     :List of 9
  ..$ : num [1:63] 33.3 40.5 50.8 51.4 52.2 ...
  ..$ : num [1:63] 67.7 68.4 68.6 69.7 70.2 ...
  ..$ : num [1:63] 65.9 66.6 67 67.7 68.4 ...
  ..$ : num [1:63] 64 62.7 64.2 64.8 65 ...
  ..$ : num [1:63] 69.2 70.2 69.5 70.3 71.1 ...
  ..$ : num [1:63] 69.1 69.6 69.8 69.9 70.4 ...
  ..$ : num [1:63] 46.1 46.9 47.6 48.3 49.2 ...
  ..$ : num [1:63] 45.8 46.5 47 47.4 47.7 ...
  ..$ : num [1:63] 63 62.9 63.4 63.8 63.8 ...
 - attr(*, "spec")=
  .. cols(
  ..   iso2c = col_character(),
  ..   iso3c = col_character(),
  ..   country = col_character(),
  ..   region = col_character(),
  ..   `1980_gdp_capita` = col_double(),
  ..   `1980_life_exp` = col_double(),
  ..   `2020_gdp_capita` = col_double(),
  ..   `2020_life_exp` = col_double()
  .. )
 - attr(*, "problems")=<externalptr> 

Let’s now use this new dataset to create a table. Because we will show life expectancy using a sparkline, we don’t need to select the 1980 and 2020 values for this variable In the code below, you’ll recognize that we build the table as we already did. However, as we add life expectancy as a sparkline, we omit the fmt_number and set a label for the sparkline and remove the spanner. At the end, we add {gtExtras}’ function gt_plt_sparkline(). The function includes a number of option, e.g. to add reference lines showing median or mean values, …, set the color palette. To see these options, you can check the function. Here, we only include the column which holds the sparkline data:

life_table_spark |> select(iso2c, iso3c, region, `1980_gdp_capita`, `2020_gdp_capita`, data_spark) |>
  gt(groupname_col = "region") |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "World Bank Development Indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)",
    cells_body(columns = 1, rows = 6), 
    placement = "right") |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  cols_label(
    iso2c = " ",
    `1980_gdp_capita` = md("*1980*"),
    `2020_gdp_capita` = md("*2020*"),
    data_spark = md("*Life expectancy*")) |>
  fmt_country(columns = 2) |>
  fmt_flag(columns = 1) |>
  cols_merge(columns = 1:2) |>
  gt_plt_sparkline(data_spark)
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020
East Asia & Pacific
China $430.86 10358.170 78.6
Japan $19,334.37 34650.797 84.0
Hong Kong $12,553.22 41451.326 83.7
Europe & Central Asia
Portugal $10,832.52 19778.714 81.6
Bulgaria $3,431.52 7990.693 74.4
Germany1 $23,977.27 42362.647 80.7
Latin America & Caribbean
Nicaragua $1,850.01 1959.788 74.6
Guatemala $3,287.02 4005.839 68.7
Trinidad & Tobago $8,612.16 15789.614 74.7
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

As an alternative using gl_plt_bullet you can add a bullet chart. These charts show the value of life expectancy and add a reference line which shows the a summary statistic using the data in the table. In the example below, we add the mean value for each group as the target. Do so do, we first need to add this mean to the dataset. We do so in two columns, one for each year, and label these columns target_col1 and target_col2. At the end of the code, we use gl_plt_bullet() to create a bullet chart: a bar to show the value for each country and a vertical line to show the target. You can find all options for this function in the reference guide. Here, we include three: we set the width of the bullet plot and add a color palette to fill the bar and show the target:

life_table_spark |> 
  dplyr::group_by(region) |> 
  dplyr::mutate(
    target_col1 = mean(`1980_life_exp`),
    target_col2 = mean(`2020_life_exp`)) |> 
  dplyr::ungroup() |> 
  dplyr::select(iso2c, iso3c, region, `1980_gdp_capita`, `2020_gdp_capita`, `1980_life_exp`, `2020_life_exp`, target_col1, target_col2) |>
  gt(groupname_col = "region") |>
  fmt_currency(
    columns = 3:4,
    decimals = 2,
    currency = "USD") |>
  tab_header(
    title = "GDP per capita and life expectancy at birth", 
    subtitle = "Selected countries and years") |>
  tab_source_note(
    source_note = "World Bank Development Indicators") |>
  tab_footnote(
    footnote = "Data prior to 1991 refer to Western Germany (GDR)",
    cells_body(columns = 1, rows = 6), 
    placement = "right") |>
  tab_spanner(
    label = md("**Per capita GDP**"), 
    columns = contains("gdp")) |>
  tab_spanner(
    label = md("**Life expectancy**"), 
    columns = contains("life")) |>
  cols_label(
    iso2c = " ",
    `1980_gdp_capita` = md("*1980*"),
    `2020_gdp_capita` = md("*2020*"), 
    `1980_life_exp` = md("*1980*"),
    `2020_life_exp` = md("*2020*")) |>
  fmt_country(columns = 2) |>
  fmt_flag(columns = 1) |>
  cols_merge(columns = 1:2) |>
  gt_plt_bullet(column = `1980_life_exp`, target = target_col1, width = 30,
                palette = c("lightsteelblue1", "lightsteelblue4")) |>
  gt_plt_bullet(column = `2020_life_exp`, target = target_col2, width = 30,
                palette = c("steelblue4", "steelblue1"))
GDP per capita and life expectancy at birth
Selected countries and years
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 10358.170
Japan $19,334.37 34650.797
Hong Kong $12,553.22 41451.326
Europe & Central Asia
Portugal $10,832.52 19778.714
Bulgaria $3,431.52 7990.693
Germany1 $23,977.27 42362.647
Latin America & Caribbean
Nicaragua $1,850.01 1959.788
Guatemala $3,287.02 4005.839
Trinidad & Tobago $8,612.16 15789.614
World Bank Development Indicators
1 Data prior to 1991 refer to Western Germany (GDR)

12.2.7 Styling

We can now add styling to the table. Styling refers to e.g. colors used to fill cells or print lines, font used in the text, the width of the table as well as the columns, … . {gt} includes a number of quick styling functions who generally start with opt_. These functions offer a quick way to set, e.g. the font for the entire table opt_table_font(), to set line opt_table_lines("all", "none", "default") where all includes all possible lines, none shows no lines and default with the default (horizontal) lines, to align the table header opt_align_table_header(align = "left", "center", "right"), … . The function opt_style() allows you to quickly set 6 styles using 6 colors: “blue”, “cyan”, “pink”, “green”, “red” and “gray”. For instance using style = 6 and color = "blue", opt_style() shows this table:

life_table_gt_lab |> 
  opt_stylize(style = 6, color = "blue")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Changing the style to 1 and the color to pink and adding summary rows to see how these are styled:

life_table_gt_lab |>
  summary_rows(
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD"),
    missing_text = "") |>
  summary_rows(
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2), 
    missing_text = "") |>
  opt_stylize(style = 1, color = "pink")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10 71.69 82.71
Minimum
$430.86 $10,358.17 64.42 78.08
Maximum
$19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35 71.72 78.56
Minimum
$3,431.52 $7,990.69 71.16 73.66
Maximum
$23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$4,583.06 $7,251.75 60.50 72.67
Minimum
$1,850.01 $1,959.79 56.02 71.80
Maximum
$8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

{gtExtras} includes a number of themes, e.g. a theme base on the English newspaper the Guardian (gt_theme_guardian(gt_object, ...)). If you want to use this theme, you can add at at the end of the table.

life_table_gt_lab |>
  summary_rows(
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD"),
    missing_text = "") |>
  summary_rows(
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2), 
    missing_text = "") |>
  gt_theme_guardian()
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10 71.69 82.71
Minimum
$430.86 $10,358.17 64.42 78.08
Maximum
$19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35 71.72 78.56
Minimum
$3,431.52 $7,990.69 71.16 73.66
Maximum
$23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$4,583.06 $7,251.75 60.50 72.67
Minimum
$1,850.01 $1,959.79 56.02 71.80
Maximum
$8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

Let’s now try to add some tailored styling. Setting a style is usually something you don’t need to do for every table. For instance, the font family, background colors or table headings or lines are often similar across tables. In other words, if you set these values once, you will be not to repeat these steps for subsequent tables. Before we do so, we first assign the table to life_table_gt_sum:

life_table_gt_sum <- life_table_gt_lab |>
  summary_rows(
    columns = 3:4,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_currency(
      ., 
      decimals = 2, 
      currency = "USD"),
    missing_text = "") |>
  summary_rows(
    columns = 5:6,
    fns = list(
      Average = ~mean(., na.rm = TRUE),
      Minimum = ~min(., na.rm = TRUE), 
      Maximum = ~max(., na.rm = TRUE)),
    fmt = ~fmt_number(
      ., 
      decimals = 2), 
    missing_text = "")

To add styling, you can use tab_options(). This function is similar to the theme() function in {ggplot2}. To see all options, I refer to the online reference for this function. In these arguments, you’ll recognize the arguments from the setup of the plot, e.g. column_labels_font_size, row_group_border_bottom_width, table_body_hlines.color … . As you can see, these arguments usually follow the same logic: first the part of the table (Figure 12.1), second the element to style and third the style option. The element to style and style options follow from the component of the table, e.g. for a label, you can add options to change the text e.g. font, size, alignment; for a border, you can set the color, the width or style, … . I’ll illustrate some of these options here:

  • Add some styling to the table header:
life_table_gt_sum |>
  tab_options(
  heading.title.font.size = px(20),
  heading.title.font.weight = "bold",
  heading.subtitle.font.size = px(15),
  heading.subtitle.font.weight = "lighter",
  heading.border.bottom.style = "double",
  heading.border.bottom.color = "lavenderblush4",
  heading.background.color = "lavenderblush2")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10 71.69 82.71
Minimum
$430.86 $10,358.17 64.42 78.08
Maximum
$19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35 71.72 78.56
Minimum
$3,431.52 $7,990.69 71.16 73.66
Maximum
$23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$4,583.06 $7,251.75 60.50 72.67
Minimum
$1,850.01 $1,959.79 56.02 71.80
Maximum
$8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)
  • Add some styling to the row groups, spanners and column labels:
life_table_gt_sum |>
  tab_options(
  heading.title.font.size = px(20),
  heading.subtitle.font.size = px(15),
  heading.subtitle.font.weight = "lighter",
  heading.border.bottom.style = "double",
  heading.border.bottom.color = "lavenderblush4",
  heading.background.color = "lavenderblush2", 
  row_group.background.color = "lightsteelblue1", 
  row_group.font.weight = "bold", 
  column_labels.background.color = "lightgrey")
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10 71.69 82.71
Minimum
$430.86 $10,358.17 64.42 78.08
Maximum
$19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35 71.72 78.56
Minimum
$3,431.52 $7,990.69 71.16 73.66
Maximum
$23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$4,583.06 $7,251.75 60.50 72.67
Minimum
$1,850.01 $1,959.79 56.02 71.80
Maximum
$8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)
  • Add some styling to the footnote and source note:
life_table_gt_sum |>
  tab_options(
  heading.title.font.size = px(20),
  heading.subtitle.font.size = px(15),
  heading.subtitle.font.weight = "lighter",
  heading.border.bottom.style = "double",
  heading.border.bottom.color = "lavenderblush4",
  heading.background.color = "lavenderblush2", 
  row_group.background.color = "lightsteelblue1", 
  row_group.font.weight = "bold",
  column_labels.background.color = "lightgrey", 
  footnotes.font.size = px(8), 
  source_notes.font.size = px(8))
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10 71.69 82.71
Minimum
$430.86 $10,358.17 64.42 78.08
Maximum
$19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35 71.72 78.56
Minimum
$3,431.52 $7,990.69 71.16 73.66
Maximum
$23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$4,583.06 $7,251.75 60.50 72.67
Minimum
$1,850.01 $1,959.79 56.02 71.80
Maximum
$8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

In addition to the tab_options function, {gt} includes two tab_style function allowing you to target specific cells. Using both, you first include the style and second the location. For the style, you can add cell_text, cell_fill or cell_border. Each of these include arguments relevant for the style (text size, fill color, border width, …). In addition you add a location to identify the location of a cell. To do so, you add e.g. cells_row_groups, cells_summary, cells_title, … . Combining style and location, you can target individual cells to style. If you want to change more than one style component, you add them in a list (e.g. list(cell_fill(), cell_text()). Doing so, you can change multiple style elements in a cell. If the same styling applies to multiple location, you add collect these too in a list, e.g. list(cells_summary(), cells_stub_summary()). As an example, here we will align the column labels “center”, fill the cell_row_groups in lightsteeblue1, set the text to “bold” and the borders at the top and the bottom to a width of 3 pixels and using #B0C4DE as the color. In addition, for the summary cells, we set the size of the text to 14 pixels and add color and width to their borders. We add the color steelblue2 to the header and set the size of the text using 25 pixels and add a color to the 1, 3, 4, 6, 7 and 9: lightblue.

life_table_gt_sum |>
    tab_style(
    style = cell_text(align = "center"), 
    locations = list(cells_column_labels(), cells_column_spanners())) |>
  tab_style(
    style = 
      list(
        cell_fill(color = "lightsteelblue1"), 
        cell_text(weight = "bold"), 
        cell_borders(
          sides = c("top", "bottom"), 
          color = "#B0C4DE", 
          weight = px(3))),
    locations = cells_row_groups()) |>
  tab_style(
    style = list(cell_text(weight = "lighter", size = px(14)), cell_borders(sides = "top", color = "#B0C4DE", weight = px(2))), 
    locations = list(cells_summary(), cells_stub_summary())) |>
  tab_style(
    style = list(cell_fill(color = "steelblue2"), cell_text(weight = "bolder"), size = px(25)), 
    locations = cells_title()) |>
    tab_style(
    style = list(cell_fill(color = "lightblue")),
    locations = cells_body(rows = c(1, 3, 4, 6, 7, 9)))
GDP per capita and life expectancy at birth
Selected countries and years
Country
Per capita GDP
Life expectancy
1980 2020 1980 2020
East Asia & Pacific
China $430.86 $10,358.17 64.42 78.08
Japan $19,334.37 $34,650.80 75.99 84.56
Hong Kong SAR, China $12,553.22 $41,451.33 74.65 85.50
Average
$10,772.82 $28,820.10 71.69 82.71
Minimum
$430.86 $10,358.17 64.42 78.08
Maximum
$19,334.37 $41,451.33 75.99 85.50
Europe & Central Asia
Portugal $10,832.52 $19,778.71 71.21 80.98
Bulgaria $3,431.52 $7,990.69 71.16 73.66
Germany $23,977.271 $42,362.65 72.801 81.04
Average
$12,747.10 $23,377.35 71.72 78.56
Minimum
$3,431.52 $7,990.69 71.16 73.66
Maximum
$23,977.27 $42,362.65 72.80 81.04
Latin America & Caribbean
Nicaragua $1,850.01 $1,959.79 57.91 71.80
Guatemala $3,287.02 $4,005.84 56.02 71.80
Trinidad and Tobago $8,612.16 $15,789.61 67.57 74.41
Average
$4,583.06 $7,251.75 60.50 72.67
Minimum
$1,850.01 $1,959.79 56.02 71.80
Maximum
$8,612.16 $15,789.61 67.57 74.41
Source: World Bank Development indicators
1 Data prior to 1991 refer to Western Germany (GDR)

tab_style_body() allows you to fill individual cells.

Note that adding color to cells has to be consistent with earlier formatting using e.g. data_color(): if some cells are filled using data_color() there would be an inconsistency if you fill the cells of the body of the table using tab_style. In addition, adding color with a plot, a flag or another image risks reducing the visual attraction of that plot, flag or image. Here again, adding color is something that you need to do carefully. As a last remark: it is not because {gt} and {gtExtras} allow you to change almost all components of a table that you actually have to do that. Often trying to limit the number of fills, border styles of widths will improve your table. Here, I refer to Tom Mock’s 10 Guidelines with gt for more information on how you can build good (and bad) tables.